package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"time"

	monproto "gollect/proto"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"gollect/probe/collect/cpu"
	"gollect/probe/collect/disk"
	"gollect/probe/collect/iomonitor"
	"gollect/probe/collect/memory"
	"gollect/probe/collect/process"
	"gollect/probe/collect/user"
)

type monitoringServer struct {
	monproto.UnimplementedMonitoringServiceServer
	config *Config
}

// formatBytes converts bytes to human readable format
func formatBytes(bytes uint64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

// printDiskInfo prints detailed disk usage information
func printDiskInfo(diskInfo *monproto.DiskInfo) {
	fmt.Println("=== DISK USAGE INFORMATION ===")
	if len(diskInfo.Partitions) == 0 {
		fmt.Println("No disk partitions found")
		return
	}

	for i, partition := range diskInfo.Partitions {
		fmt.Printf("Partition %d:\n", i+1)
		fmt.Printf("  Mountpoint: %s\n", partition.Mountpoint)
		fmt.Printf("  Total Space: %s\n", formatBytes(partition.Total))
		fmt.Printf("  Used Space: %s (%.1f%%)\n", formatBytes(partition.Used), partition.UsedPercent*100)
		fmt.Printf("  Free Space: %s\n", formatBytes(partition.Free))
		fmt.Printf("  Usage: [")

		// Create a visual progress bar
		barLength := 20
		usedBars := int(float64(barLength) * float64(partition.UsedPercent))
		for j := 0; j < barLength; j++ {
			if j < usedBars {
				fmt.Print("█")
			} else {
				fmt.Print("░")
			}
		}
		fmt.Printf("] %.1f%%\n", partition.UsedPercent*100)
		fmt.Println()
	}
}

// printCPUInfo prints CPU usage information
func printCPUInfo(cpuInfo *monproto.CpuInfo) {
	fmt.Println("=== CPU USAGE INFORMATION ===")
	fmt.Printf("Total CPU Load: %.2f%%\n", cpuInfo.TotalLoad*100)

	if len(cpuInfo.CoreLoads) > 0 {
		fmt.Println("Per-Core Usage:")
		for i, load := range cpuInfo.CoreLoads {
			fmt.Printf("  Core %d: %.2f%%\n", i+1, load*100)
		}
	}
	fmt.Println()
}

// printMemoryInfo prints memory usage information
func printMemoryInfo(memoryInfo *monproto.MemoryInfo) {
	fmt.Println("=== MEMORY USAGE INFORMATION ===")
	fmt.Printf("Total Memory: %s\n", formatBytes(memoryInfo.Total))
	fmt.Printf("Used Memory: %s (%.1f%%)\n", formatBytes(memoryInfo.Used), memoryInfo.UsedPercent*100)
	fmt.Printf("Free Memory: %s\n", formatBytes(memoryInfo.Free))

	// Create a visual progress bar
	fmt.Printf("Usage: [")
	barLength := 20
	usedBars := int(float64(barLength) * float64(memoryInfo.UsedPercent))
	for i := 0; i < barLength; i++ {
		if i < usedBars {
			fmt.Print("█")
		} else {
			fmt.Print("░")
		}
	}
	fmt.Printf("] %.1f%%\n", memoryInfo.UsedPercent*100)
	fmt.Println()
}

// printNetworkInfo prints network I/O information
func printNetworkInfo(networkInfo *monproto.NetworkInfo) {
	fmt.Println("=== NETWORK I/O INFORMATION ===")
	fmt.Printf("Total Bytes Sent: %s\n", formatBytes(networkInfo.TotalBytesSent))
	fmt.Printf("Total Bytes Received: %s\n", formatBytes(networkInfo.TotalBytesRecv))
	fmt.Println()
}

// printProcessInfo prints top process information
func printProcessInfo(processes []*monproto.JobInfo) {
	fmt.Println("=== TOP PROCESSES ===")
	if len(processes) == 0 {
		fmt.Println("No process information available")
		return
	}

	fmt.Printf("%-6s %-20s %-8s %-8s %-15s %s\n", "PID", "NAME", "CPU%", "MEM%", "UPTIME", "COMMAND")
	fmt.Println(string(make([]byte, 80, 80)))

	for i, proc := range processes {
		if i >= 10 { // Limit to top 10
			break
		}
		uptime := time.Duration(proc.UptimeSeconds * float64(time.Second))
		fmt.Printf("%-6d %-20s %-8.2f %-8.2f %-15s %s\n",
			proc.RootPid,
			truncateString(proc.Name, 18),
			proc.Cpu*100,
			proc.Mem*100,
			formatDuration(uptime),
			truncateString(proc.Cmd, 30))
	}
	fmt.Println()
}

// printUserInfo prints top user information
func printUserInfo(users []*monproto.UserInfo) {
	fmt.Println("=== TOP USERS ===")
	if len(users) == 0 {
		fmt.Println("No user information available")
		return
	}

	fmt.Printf("%-15s %-8s %-8s\n", "USER", "CPU%", "MEM%")
	fmt.Println(string(make([]byte, 35, 35)))

	for i, user := range users {
		if i >= 5 { // Limit to top 5
			break
		}
		fmt.Printf("%-15s %-8.2f %-8.2f\n",
			truncateString(user.User, 13),
			user.Cpu*100,
			user.Mem*100)
	}
	fmt.Println()
}

// truncateString truncates a string to the specified length
func truncateString(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen-3] + "..."
}

// formatDuration formats a duration in a human-readable way
func formatDuration(d time.Duration) string {
	if d < time.Minute {
		return fmt.Sprintf("%ds", int(d.Seconds()))
	} else if d < time.Hour {
		return fmt.Sprintf("%dm", int(d.Minutes()))
	} else {
		hours := int(d.Hours())
		minutes := int(d.Minutes()) % 60
		return fmt.Sprintf("%dh%dm", hours, minutes)
	}
}

func (s *monitoringServer) GetMonitoringData(ctx context.Context, req *monproto.MonitoringRequest) (*monproto.MonitoringEnvelope, error) {
	log.Printf("Received monitoring data request")

	timestamp := time.Now().UnixNano()

	// Collect all data points concurrently
	cpuInfo := cpu.Collect(time.Duration(s.config.CPUInterval) * time.Second)
	memoryInfo := memory.Collect(time.Duration(s.config.MemoryInterval) * time.Second)
	diskInfo := disk.Collect(time.Duration(s.config.DiskInterval) * time.Second)
	iomonitor.Collect(time.Duration(s.config.IOInterval) * time.Second)
	processInfo := process.CollectTopN(10, time.Duration(s.config.ProcessInterval)*time.Second)
	userInfo := user.CollectTopN(5, time.Duration(s.config.UserInterval)*time.Second)

	// Print verbose information if enabled
	if s.config.Verbose {
		fmt.Printf("\n=== MONITORING DATA COLLECTED AT %s ===\n", time.Now().Format("2006-01-02 15:04:05"))

		// Always print disk information when verbose is enabled
		printDiskInfo(diskInfo)

		// Print other information based on what's being sent
		cycle := (time.Now().UnixNano() / int64(time.Second)) % 5
		switch cycle {
		case 0, 2:
			printCPUInfo(cpuInfo)
		case 1, 3:
			printMemoryInfo(memoryInfo)
		case 4:
			// For case 4, check the sub-cycle to determine what to print
			subCycle := (time.Now().UnixNano() / int64(5*time.Second)) % 3
			switch subCycle {
			case 0:
				// Disk info is already printed above
			case 1:
				printProcessInfo(processInfo)
			case 2:
				printUserInfo(userInfo)
			}
		}
	}

	// For now, we'll cycle through different data types on each request
	// In the future, we can add a field to the request to specify what data type to return

	// Get a simple counter to cycle through data types
	// Use a 5-second cycle to get disk/process/user data every 5 seconds
	// CPU and memory will be sent more frequently
	cycle := (time.Now().UnixNano() / int64(time.Second)) % 5

	var envelope *monproto.MonitoringEnvelope

	switch cycle {
	case 0:
		// CPU data (every 1 second)
		log.Printf("Sending CPU data...")
		envelope = &monproto.MonitoringEnvelope{
			Timestamp: timestamp,
			Payload:   &monproto.MonitoringEnvelope_Cpu{Cpu: cpuInfo},
		}
	case 1:
		// Memory data (every 1 second)
		log.Printf("Sending Memory data...")
		envelope = &monproto.MonitoringEnvelope{
			Timestamp: timestamp,
			Payload:   &monproto.MonitoringEnvelope_Memory{Memory: memoryInfo},
		}
	case 2:
		// CPU data again (every 1 second)
		log.Printf("Sending CPU data (cycle 2)...")
		envelope = &monproto.MonitoringEnvelope{
			Timestamp: timestamp,
			Payload:   &monproto.MonitoringEnvelope_Cpu{Cpu: cpuInfo},
		}
	case 3:
		// Memory data again (every 1 second)
		log.Printf("Sending Memory data (cycle 2)...")
		envelope = &monproto.MonitoringEnvelope{
			Timestamp: timestamp,
			Payload:   &monproto.MonitoringEnvelope_Memory{Memory: memoryInfo},
		}
	case 4:
		// Disk, Process, or User data (every 5 seconds, rotating)
		// Use a sub-cycle to rotate through disk, process, and user data
		subCycle := (time.Now().UnixNano() / int64(5*time.Second)) % 3
		switch subCycle {
		case 0:
			// Disk data
			log.Printf("Sending Disk data...")
			envelope = &monproto.MonitoringEnvelope{
				Timestamp: timestamp,
				Payload:   &monproto.MonitoringEnvelope_Disk{Disk: diskInfo},
			}
		case 1:
			// Process data
			log.Printf("Sending Process data...")
			envelope = &monproto.MonitoringEnvelope{
				Timestamp: timestamp,
				Payload:   &monproto.MonitoringEnvelope_Processes{Processes: &monproto.ProcessCollection{TopProcesses: processInfo}},
			}
		case 2:
			// User data
			log.Printf("Sending User data...")
			envelope = &monproto.MonitoringEnvelope{
				Timestamp: timestamp,
				Payload:   &monproto.MonitoringEnvelope_Users{Users: &monproto.UserCollection{TopUsers: userInfo}},
			}
		}
	}

	log.Printf("Returning monitoring data envelope")
	return envelope, nil
}

func startServer(config *Config, port int) error {
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return fmt.Errorf("failed to listen: %w", err)
	}

	// Create gRPC server with insecure credentials
	s := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
	monproto.RegisterMonitoringServiceServer(s, &monitoringServer{config: config})

	log.Printf("gRPC server listening on port %d", port)
	return s.Serve(lis)
}
